#include "lyj_dokan.h"
#include "lyj_filenode.h"
#include "lyj_kv_store.h"
#include <sddl.h>


#define lyj_ebackup(dokan_file_info) ((lyj_ebackup_t *)(dokan_file_info)->DokanOptions->GlobalContext)

#define lyj_kv(dokan_file_info) (lyj_ebackup(dokan_file_info)->kv_store)
#define lyj_fn(dokan_file_info) ((lyj_filenode_t* )((dokan_file_info)->Context))


static NTSTATUS DOKAN_CALLBACK  lyj_dokan_create_file(LPCWSTR file_name, PDOKAN_IO_SECURITY_CONTEXT security_context, ACCESS_MASK desiredaccess, ULONG fileattributes, ULONG shareaccess, ULONG createdisposition, ULONG createoptions, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls", file_name);
    NTSTATUS ret;
    ACCESS_MASK generic_desired_access;
    DWORD creation_disposition;
    DWORD file_attributes_and_flags;
    lyj_filenode_t* fn = lyj_type_malloc(lyj_filenode_t);
    lyj_filenode_initw(fn, file_name);
    dokan_file_info->Context = (ULONG64)fn;
    DokanMapKernelToUserCreateFileFlags(
        desiredaccess, fileattributes, createoptions, createdisposition,
        &generic_desired_access, &file_attributes_and_flags,
        &creation_disposition);
    lyj_filenode_get(lyj_kv(dokan_file_info), fn);

    lyj_bool_t is_exist = fn->meta.is_exist;
    lyj_wlog(L"lyj_filenode_get %ls is_exist:%d is_directory:%d", fn->file_name, fn->meta.is_exist, fn->meta.is_directory);
    if (fn->meta.is_exist && fn->meta.is_directory)
    {
        if (createoptions & FILE_NON_DIRECTORY_FILE)
            return STATUS_FILE_IS_A_DIRECTORY;
        dokan_file_info->IsDirectory = TRUE;
    }
    if (0 == lstrcmpW(L"\\System Volume Information", file_name) || 0 == lstrcmpW(L"\\$RECYCLE.BIN", file_name))
    {
        return STATUS_NO_SUCH_FILE;
    }
    if (dokan_file_info->IsDirectory)
    {
        lyj_wlog(L"is directory %ls", file_name);

        if (creation_disposition == CREATE_NEW ||  creation_disposition == OPEN_ALWAYS) {
            lyj_wlog(L"create Directory %ls", file_name);
            
            if (fn->meta.is_exist) return STATUS_OBJECT_NAME_COLLISION;
            fn->meta.is_directory = LYJ_TRUE;
            fn->meta.attributes = FILE_ATTRIBUTE_DIRECTORY;
            return lyj_filenode_add(lyj_kv(dokan_file_info), fn, security_context);
        }

        if (fn->meta.is_exist && !fn->meta.is_directory) return STATUS_NOT_A_DIRECTORY;
        if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;

        lyj_wlog(L"open directory %ls", file_name);
    }
    else
    {
        lyj_wlog(L"is file %ls", file_name);
        // Cannot overwrite an hidden or system file.
        if (fn->meta.is_exist && (((!(file_attributes_and_flags & FILE_ATTRIBUTE_HIDDEN) &&
            (fn->meta.attributes & FILE_ATTRIBUTE_HIDDEN)) ||
            (!(file_attributes_and_flags & FILE_ATTRIBUTE_SYSTEM) &&
                (fn->meta.attributes & FILE_ATTRIBUTE_SYSTEM))) &&
            (creation_disposition == TRUNCATE_EXISTING ||
                creation_disposition == CREATE_ALWAYS)))
            return STATUS_ACCESS_DENIED;

        // Cannot delete a file with readonly attributes.
        if ((fn->meta.is_exist && (fn->meta.attributes & FILE_ATTRIBUTE_READONLY) ||
            (file_attributes_and_flags & FILE_ATTRIBUTE_READONLY)) &&
            (file_attributes_and_flags & FILE_FLAG_DELETE_ON_CLOSE))
            return STATUS_CANNOT_DELETE;

        // Cannot open a readonly file for writing.
        if ((creation_disposition == OPEN_ALWAYS ||
            creation_disposition == OPEN_EXISTING) &&
            fn->meta.is_exist && (fn->meta.attributes & FILE_ATTRIBUTE_READONLY) &&
            desiredaccess & FILE_WRITE_DATA)
            return STATUS_ACCESS_DENIED;

        // Cannot overwrite an existing read only file.
        // FILE_SUPERSEDE can as it replace and not overwrite.
        if ((creation_disposition == CREATE_NEW ||
            (creation_disposition == CREATE_ALWAYS &&
                createdisposition != FILE_SUPERSEDE) ||
            creation_disposition == TRUNCATE_EXISTING) &&
            fn->meta.is_exist && (fn->meta.attributes & FILE_ATTRIBUTE_READONLY))
            return STATUS_ACCESS_DENIED;

        if (creation_disposition == CREATE_NEW ||
            creation_disposition == CREATE_ALWAYS ||
            creation_disposition == OPEN_ALWAYS ||
            creation_disposition == TRUNCATE_EXISTING) {
            // Combines the file attributes and flags specified by
            // dwFlagsAndAttributes with FILE_ATTRIBUTE_ARCHIVE.
            file_attributes_and_flags |= FILE_ATTRIBUTE_ARCHIVE;
            // We merge the attributes with the existing file attributes
            // except for FILE_SUPERSEDE.
            if (fn->meta.is_exist && createdisposition != FILE_SUPERSEDE)
                file_attributes_and_flags |= fn->meta.attributes;
            // Remove non specific attributes.
            file_attributes_and_flags &= ~FILE_ATTRIBUTE_STRICTLY_SEQUENTIAL;
            // FILE_ATTRIBUTE_NORMAL is override if any other attribute is set.
            file_attributes_and_flags &= ~FILE_ATTRIBUTE_NORMAL;
        }

        switch (creation_disposition) {
        case CREATE_ALWAYS: {
            lyj_wlog("CREATE_ALWAYS %ls", file_name);
            /*
             * Creates a new file, always.
             *
             * We handle FILE_SUPERSEDE here as it is converted to TRUNCATE_EXISTING
             * by DokanMapKernelToUserCreateFileFlags.
             */

            //if (!stream_names.second.empty()) {
            //    // The createfile is a alternate stream,
            //    // we need to be sure main stream exist
            //    auto n =
            //        create_main_stream(filenodes, filename_str, stream_names,
            //            file_attributes_and_flags, security_context);
            //    if (n != STATUS_SUCCESS) return n;
            //}
            fn->meta.attributes = file_attributes_and_flags;
            fn->meta.is_directory = LYJ_FALSE;
            lyj_bool_t is_exist = fn->meta.is_exist;
            ret = lyj_filenode_add(lyj_kv(dokan_file_info), fn, security_context);
            //auto n =
            //    filenodes->add(std::make_shared<filenode>(filename_str, false,
            //        file_attributes_and_flags,
            //        security_context),
            //        stream_names);
            if (ret != STATUS_SUCCESS) return ret;

            /*
             * If the specified file exists and is writable, the function overwrites
             * the file, the function succeeds, and last-error code is set to
             * ERROR_ALREADY_EXISTS
             */
            if (is_exist) return STATUS_OBJECT_NAME_COLLISION;
        } break;
        case CREATE_NEW: {
            lyj_wlog("CREATE_NEW %ls", file_name);
            /*
             * Creates a new file, only if it does not already exist.
             */
            if (fn->meta.is_exist) return STATUS_OBJECT_NAME_COLLISION;

            //if (!stream_names.second.empty()) {
            //    // The createfile is a alternate stream,
            //    // we need to be sure main stream exist
            //    auto n =
            //        create_main_stream(filenodes, filename_str, stream_names,
            //            file_attributes_and_flags, security_context);
            //    if (n != STATUS_SUCCESS) return n;
            //}

            fn->meta.attributes = file_attributes_and_flags;
            fn->meta.is_directory = LYJ_FALSE;
            ret = lyj_filenode_add(lyj_kv(dokan_file_info), fn, security_context);
            //auto n = filenodes->add(std::make_shared<filenode>(filename_str, false,
            //    file_attributes_and_flags,
            //    security_context),
            //    stream_names);
            if (ret != STATUS_SUCCESS) return ret;
        
        } break;
        case OPEN_ALWAYS: {
            lyj_wlog("OPEN_ALWAYS %ls", file_name);
            /*
             * Opens a file, always.
             */

            if (!fn->meta.is_exist) {
                fn->meta.attributes = file_attributes_and_flags;
                fn->meta.is_directory = LYJ_FALSE;
                ret = lyj_filenode_add(lyj_kv(dokan_file_info), fn, security_context);
                //auto n = filenodes->add(std::make_shared<filenode>(
                //    filename_str, false, file_attributes_and_flags,
                //    security_context),
                //    stream_names);
                if (ret != STATUS_SUCCESS) return ret;
            }
            else {
                if (desiredaccess & FILE_EXECUTE) {
                    fn->meta.times.lastaccess = lyj_get_currenttime();
                }
            }
        } break;
        case OPEN_EXISTING: {
            lyj_wlog("OPEN_EXISTING %ls", file_name);
            /*
             * Opens a file or device, only if it exists.
             * If the specified file or device does not exist, the function fails
             * and the last-error code is set to ERROR_FILE_NOT_FOUND
             */
            if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;

            if (desiredaccess & FILE_EXECUTE) {
                fn->meta.times.lastaccess = lyj_get_currenttime();
            }
        } break;
        case TRUNCATE_EXISTING: {

            lyj_wlog("TRUNCATE_EXISTING %ls", file_name);
            /*
             * Opens a file and truncates it so that its size is zero bytes, only if
             * it exists. If the specified file does not exist, the function fails
             * and the last-error code is set to ERROR_FILE_NOT_FOUND
             */
            if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;

            fn->meta.file_size = 0;
            fn->meta.times.lastaccess = fn->meta.times.lastwrite = lyj_get_currenttime();
            fn->meta.attributes = file_attributes_and_flags;
        } break;
        default:

            lyj_wlog("%ls Unknown CreationDisposition %d", file_name, creation_disposition);
            break;
        }

    }
    fn->generic_desired_access = generic_desired_access;
    fn->shareaccess = shareaccess;
    fn->creation_disposition = creation_disposition;

    if (is_exist && (creation_disposition == CREATE_NEW || creation_disposition == OPEN_ALWAYS))
        return STATUS_OBJECT_NAME_COLLISION;
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_get_file_information(LPCWSTR file_name, LPBY_HANDLE_FILE_INFORMATION buffer, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_filenode_t* fn = lyj_fn(dokan_file_info);
    lyj_filenode_get_meta(lyj_kv(dokan_file_info), fn);
    if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;

    buffer->dwFileAttributes = fn->meta.attributes;
    lyj_int64_to_dw_low_high(fn->meta.times.creation, &buffer->ftCreationTime.dwLowDateTime, &buffer->ftCreationTime.dwHighDateTime);
    lyj_int64_to_dw_low_high(fn->meta.times.lastaccess, &buffer->ftLastAccessTime.dwLowDateTime, &buffer->ftLastAccessTime.dwHighDateTime);
    lyj_int64_to_dw_low_high(fn->meta.times.lastwrite, &buffer->ftLastWriteTime.dwLowDateTime, &buffer->ftLastWriteTime.dwHighDateTime);
    lyj_int64_to_dw_low_high(fn->meta.file_size, &buffer->nFileSizeLow, &buffer->nFileSizeHigh);

    // We do not track the number of links to the file so we return a fake value.
    buffer->nNumberOfLinks = 1;
    buffer->dwVolumeSerialNumber = 0x19831116;

    lyj_wlog(
        L"GetFileInformation: %ls Attributes: %x Times: Creation %llx "
        L"LastAccess  %llx LastWrite %llx FileSize  %lld NumberOfLinks %d "
        L"volume_serial_number  %x",
        fn->file_name, fn->meta.attributes, fn->meta.times.creation, fn->meta.times.lastaccess,
        fn->meta.times.lastwrite, fn->meta.file_size, buffer->nNumberOfLinks,
        buffer->dwVolumeSerialNumber);

    lyj_wlog(L"end");
    return STATUS_SUCCESS;

}
typedef struct lyj_find_data_arg_s {
    PDOKAN_FILE_INFO dokan_file_info;
    PFillFindData FillFindData;
    WIN32_FIND_DATAW data;
}lyj_find_data_arg_t;

lyj_inline static void lyj_dokan_find_files_callback(void* _arg, const WCHAR* file_name, lyj_size_t file_name_len, const lyj_meta_t* meta)
{
    lyj_find_data_arg_t *arg = (lyj_find_data_arg_t*) _arg;
    lyj_memcpy(arg->data.cFileName, file_name, sizeof(WCHAR) * file_name_len);

    arg->data.cFileName[file_name_len] = L'\0';
    lyj_wlog(L"file %ls %zu", arg->data.cFileName, file_name_len);
    arg->data.dwFileAttributes = meta->attributes;
    lyj_int64_to_dw_low_high(meta->times.creation, &arg->data.ftCreationTime.dwLowDateTime, &arg->data.ftCreationTime.dwHighDateTime);
    lyj_int64_to_dw_low_high(meta->times.lastaccess, &arg->data.ftLastAccessTime.dwLowDateTime, &arg->data.ftLastAccessTime.dwHighDateTime);
    lyj_int64_to_dw_low_high(meta->times.lastwrite, &arg->data.ftLastWriteTime.dwLowDateTime, &arg->data.ftLastWriteTime.dwHighDateTime);

    lyj_int64_to_dw_low_high(meta->file_size, &arg->data.nFileSizeLow, &arg->data.nFileSizeHigh);
    lyj_wlog(
        L"FindFiles: %ls fileNode: %ls Attributes: %d Times: Creation %lld "
        L"LastAccess %lld LastWrite %lld FileSize %lld FileIndex: %lld",
        lyj_fn(arg->dokan_file_info)->file_name, arg->data.cFileName, arg->data.dwFileAttributes,
        meta->times.creation, meta->times.lastaccess, meta->times.lastwrite, meta->file_size, meta->file_index);
    arg->FillFindData(&arg->data, arg->dokan_file_info);
}
static NTSTATUS DOKAN_CALLBACK lyj_dokan_find_files(LPCWSTR file_name, PFillFindData fill_find_data, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_find_data_arg_t arg;
    lyj_type_memzero(&arg);
    arg.dokan_file_info = dokan_file_info;
    arg.FillFindData = fill_find_data;
    lyj_filenode_children(lyj_kv(dokan_file_info), lyj_fn(dokan_file_info), &arg, lyj_dokan_find_files_callback);
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}


static NTSTATUS DOKAN_CALLBACK lyj_dokan_find_files_with_pattern(LPCWSTR path_name, LPCWSTR search_pattern, PFillFindData fill_find_data, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls", path_name);
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_set_file_attributes(LPCWSTR file_name, DWORD file_attributes, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_filenode_t* fn = lyj_fn(dokan_file_info);

    lyj_filenode_get_meta(lyj_kv(dokan_file_info), fn);
    if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;

    // from https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw
    DWORD const attributes_allowed_to_set =
        FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NORMAL |
        FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_OFFLINE |
        FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM |
        FILE_ATTRIBUTE_TEMPORARY;

    file_attributes &= attributes_allowed_to_set;

    DWORD new_file_attributes =
        (fn->meta.attributes & ~attributes_allowed_to_set) | file_attributes;

    // FILE_ATTRIBUTE_NORMAL is overriden if any other attribute is set
    if ((new_file_attributes & FILE_ATTRIBUTE_NORMAL) &&
        (new_file_attributes & ~(DWORD)(FILE_ATTRIBUTE_NORMAL)))
        new_file_attributes &= ~(DWORD)(FILE_ATTRIBUTE_NORMAL);

    fn->meta.attributes = new_file_attributes;
    lyj_filenode_set(lyj_kv(dokan_file_info), fn);
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}


static NTSTATUS DOKAN_CALLBACK lyj_dokan_set_file_time(LPCWSTR file_name, CONST FILETIME* creation_time, CONST FILETIME* last_access_time, CONST FILETIME* last_write_time, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_delete_file(LPCWSTR file_name, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_filenode_t* fn = lyj_fn(dokan_file_info);

    lyj_filenode_get_meta(lyj_kv(dokan_file_info), fn);
    if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;
    if (fn->meta.is_directory) return STATUS_ACCESS_DENIED;
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}
static NTSTATUS DOKAN_CALLBACK lyj_dokan_delete_directory(LPCWSTR file_name, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_filenode_t* fn = lyj_fn(dokan_file_info);

    lyj_filenode_get_meta(lyj_kv(dokan_file_info), fn);
    if (!fn->meta.is_exist || !fn->meta.is_directory) return STATUS_OBJECT_NAME_NOT_FOUND;
    if (lyj_filenode_children_exist(lyj_kv(dokan_file_info), lyj_fn(dokan_file_info)))
    {
        return STATUS_DIRECTORY_NOT_EMPTY;
    }
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}
static NTSTATUS DOKAN_CALLBACK lyj_dokan_move_file(LPCWSTR file_name, LPCWSTR new_file_name, BOOL replace_if_existing, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_filenode_t* old_fn = lyj_fn(dokan_file_info);

    lyj_filenode_get_meta(lyj_kv(dokan_file_info), old_fn);
    if (!old_fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;
    lyj_filenode_t new_fn;
    lyj_filenode_initw(&new_fn, new_file_name);
    lyj_filenode_get(lyj_kv(dokan_file_info), &new_fn);
    if (new_fn.meta.is_exist)
    {
        if (!replace_if_existing) return STATUS_OBJECT_NAME_COLLISION;
        if (new_fn.meta.attributes & FILE_ATTRIBUTE_READONLY) return STATUS_ACCESS_DENIED;
        if (old_fn->meta.is_directory || new_fn.meta.is_directory) return STATUS_ACCESS_DENIED;
    }
    
    return lyj_filenode_move(lyj_kv(dokan_file_info), old_fn, &new_fn);
    //lyj_wlog(L"end");
    //return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_lock_file(LPCWSTR file_name, LONGLONG byte_offset, LONGLONG length, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_wlog(L"end");
    return STATUS_NOT_IMPLEMENTED;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_unlock_file(LPCWSTR file_name, LONGLONG byte_offset, LONGLONG length, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_wlog(L"end");
    return STATUS_NOT_IMPLEMENTED;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_get_file_security(LPCWSTR file_name, PSECURITY_INFORMATION security_information, PSECURITY_DESCRIPTOR security_descriptor, ULONG buffer_length, PULONG length_needed, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_filenode_t* fn = lyj_fn(dokan_file_info);

    lyj_filenode_get_meta(lyj_kv(dokan_file_info), fn);
    if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;
    lyj_filenode_security_get(lyj_kv(dokan_file_info), fn);
    if(!fn->security.buf || fn->security.len == 0) return STATUS_NOT_IMPLEMENTED;

    // We have a Security Descriptor but we need to extract only informations
    // requested 1 - Convert the Security Descriptor to SDDL string with the
    // informations requested
    LPTSTR pStringBuffer = NULL;
    if (!ConvertSecurityDescriptorToStringSecurityDescriptor(
        fn->security.buf, SDDL_REVISION_1, *security_information,
        &pStringBuffer, NULL)) {
        return STATUS_NOT_IMPLEMENTED;
    }

    // 2 - Convert the SDDL string back to Security Descriptor
    PSECURITY_DESCRIPTOR SecurityDescriptorTmp = NULL;
    ULONG Size = 0;
    if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
        pStringBuffer, SDDL_REVISION_1, &SecurityDescriptorTmp, &Size)) {
        LocalFree(pStringBuffer);
        return STATUS_NOT_IMPLEMENTED;
    }
    LocalFree(pStringBuffer);

    *length_needed = Size;
    if (Size > buffer_length) {
        LocalFree(SecurityDescriptorTmp);
        return STATUS_BUFFER_OVERFLOW;
    }

    // 3 - Copy the new SecurityDescriptor to destination
    memcpy(security_descriptor, SecurityDescriptorTmp, Size);
    LocalFree(SecurityDescriptorTmp);

    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_set_file_security(LPCWSTR file_name, PSECURITY_INFORMATION security_information, PSECURITY_DESCRIPTOR security_descriptor, ULONG security_descriptor_length, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    static GENERIC_MAPPING memfs_mapping = { FILE_GENERIC_READ, FILE_GENERIC_WRITE,
                                        FILE_GENERIC_EXECUTE,
                                        FILE_ALL_ACCESS };
    lyj_filenode_t* fn = lyj_fn(dokan_file_info);

    lyj_filenode_get_meta(lyj_kv(dokan_file_info), fn);
    if (!fn->meta.is_exist)return STATUS_OBJECT_NAME_NOT_FOUND;

    // SetPrivateObjectSecurity - ObjectsSecurityDescriptor
    // The memory for the security descriptor must be allocated from the process
    // heap (GetProcessHeap) with the HeapAlloc function.
    // https://devblogs.microsoft.com/oldnewthing/20170727-00/?p=96705
    HANDLE pHeap = GetProcessHeap();
    PSECURITY_DESCRIPTOR heapSecurityDescriptor =
        HeapAlloc(pHeap, 0, fn->security.len);
    if (!heapSecurityDescriptor) return STATUS_INSUFFICIENT_RESOURCES;
    // Copy our current descriptor into heap memory
    memcpy(heapSecurityDescriptor, fn->security.buf, fn->security.len);

    if (!SetPrivateObjectSecurity(*security_information, security_descriptor,
        &heapSecurityDescriptor, &memfs_mapping, 0)) {
        HeapFree(pHeap, 0, heapSecurityDescriptor);
        return DokanNtStatusFromWin32(GetLastError());
    }
    lyj_filenode_security_set(lyj_kv(dokan_file_info), fn, heapSecurityDescriptor, GetSecurityDescriptorLength(heapSecurityDescriptor));

    HeapFree(pHeap, 0, heapSecurityDescriptor);
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_get_volume_information(LPWSTR volume_name_buffer, DWORD volume_name_size, LPDWORD volume_serial_number, LPDWORD maximum_component_length, LPDWORD file_system_flags, LPWSTR file_system_name_buffer, DWORD file_system_name_size, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start");
    wcscpy_s(volume_name_buffer, volume_name_size, L"KVFS");


    *volume_serial_number = 0x19831116;
    *maximum_component_length = 255;
    *file_system_flags = FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES | FILE_SUPPORTS_REMOTE_STORAGE | FILE_UNICODE_ON_DISK | FILE_NAMED_STREAMS;
    //*file_system_flags = FILE_SUPPORTS_REMOTE_STORAGE | FILE_UNICODE_ON_DISK | FILE_PERSISTENT_ACLS | FILE_NAMED_STREAMS;

    wcscpy_s(file_system_name_buffer, file_system_name_size, L"exFAT");
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_dokan_get_disk_free_space(PULONGLONG free_bytes_available, PULONGLONG total_number_of_bytes, PULONGLONG total_number_of_free_bytes, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start");
    *free_bytes_available = MAXLONGLONG;
    *total_number_of_bytes = MAXLONGLONG;
    *total_number_of_free_bytes = MAXLONGLONG;
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_mounted(LPCWSTR mount_point, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls", mount_point);
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_unmounted(PDOKAN_FILE_INFO dokan_file_info) {

    lyj_wlog(L"start");
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static void DOKAN_CALLBACK lyj_dokan_cleanup(LPCWSTR file_name, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls",file_name, lyj_fn(dokan_file_info)->file_name);
    if (dokan_file_info->DeleteOnClose) {
        // Delete happens during cleanup and not in close event.
        lyj_wlog(L"DeleteOnClose: %ls", file_name);
        lyj_filenode_delete(lyj_kv(dokan_file_info), lyj_fn(dokan_file_info));
    }
    lyj_wlog(L"end");
}
static void DOKAN_CALLBACK lyj_dokan_close_file(LPCWSTR file_name, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls",  file_name, lyj_fn(dokan_file_info)->file_name);

    lyj_filenode_destroy(lyj_fn(dokan_file_info));
    lyj_wlog(L"end");
}


static NTSTATUS DOKAN_CALLBACK lyj_dokan_read_file(LPCWSTR file_name, LPVOID buffer, DWORD buffer_length, LPDWORD read_length, LONGLONG _offset, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls size:%d offset:%lld", file_name, lyj_fn(dokan_file_info)->file_name, buffer_length, _offset);
    lyj_filenode_t* fn = lyj_fn(dokan_file_info);

    lyj_filenode_get_meta(lyj_kv(dokan_file_info), fn);
    if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;
    if (_offset < 0)
    {
        *read_length = 0;
        return STATUS_SUCCESS;
    }
    lyj_size_t offset = (lyj_size_t)_offset;
    if (offset >= fn->meta.file_size)
    {
        *read_length = 0;
        return STATUS_SUCCESS;
    }
    if (buffer_length + offset > fn->meta.file_size)
    {
        buffer_length = (DWORD)(fn->meta.file_size - offset);
    }
    lyj_size_t read_size = 0;
    if (offset < LYJ_HEAD_BUFFER_SIZE)
    {
#if LYJ_HEADER_BUFFER_KV
        lyj_filenode_head_buffer_get(lyj_kv(dokan_file_info), fn);
        read_size = lyj_min(buffer_length, fn->head_buffer.len - offset);
        if (read_size)
        {
            lyj_memcpy(buffer, (char *)fn->head_buffer.buf + offset, read_size);
        }
#else
        read_size = lyj_filenode_head_buffer_get(lyj_kv(dokan_file_info), fn, buffer, buffer_length, offset);
#endif
    }
    read_size += lyj_filenode_buffer_get(lyj_kv(dokan_file_info), fn, (char *)buffer + read_size, buffer_length - read_size,  read_size + offset);
    *read_length = (DWORD)read_size;

    lyj_wlog(L"end %ls size:%d offset:%lld read_size:%zu", fn->file_name, buffer_length, offset, read_size);
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_write_file(LPCWSTR file_name, LPCVOID buffer, DWORD number_of_bytes_to_write, LPDWORD number_of_bytes_written, LONGLONG offset, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls size:%d offset:%lld", file_name, lyj_fn(dokan_file_info)->file_name, number_of_bytes_to_write, offset);
    lyj_filenode_t* fn = lyj_fn(dokan_file_info);
    lyj_filenode_get_meta(lyj_kv(dokan_file_info), fn);
    if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;
    if (offset == -1) offset = fn->meta.file_size;
    lyj_size_t written_size = 0;
    if (offset < LYJ_HEAD_BUFFER_SIZE)
    {
#if LYJ_HEADER_BUFFER_KV
        lyj_filenode_head_buffer_get(lyj_kv(dokan_file_info), fn);
        written_size = lyj_min(LYJ_HEAD_BUFFER_SIZE - offset, number_of_bytes_to_write);
        if (written_size)
        {
            lyj_memcpy((char *)fn->head_buffer.buf + offset, buffer, written_size);
            fn->head_buffer.len = lyj_max(fn->head_buffer.len, offset + written_size);
            lyj_filenode_head_buffer_set(lyj_kv(dokan_file_info), fn);
        }
#else
        written_size = lyj_filenode_head_buffer_set(lyj_kv(dokan_file_info), fn, buffer, number_of_bytes_to_write, offset);
#endif
    }

    written_size += lyj_filenode_buffer_set(lyj_kv(dokan_file_info), fn, (char*)buffer + written_size, number_of_bytes_to_write - written_size, written_size + offset);
    if (written_size + offset > fn->meta.file_size)
    {
        fn->meta.file_size = written_size + offset;
        lyj_filenode_set_meta(lyj_kv(dokan_file_info), fn);
    }
    *number_of_bytes_written = (DWORD)written_size;

    lyj_wlog(L"end %ls size:%d offset:%lld write_size:%zu", fn->file_name, number_of_bytes_to_write, offset, written_size);
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_flush_file_buffers(LPCWSTR file_name, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_set_end_of_file(LPCWSTR file_name, LONGLONG byte_offset, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_filenode_t* fn = lyj_fn(dokan_file_info);
    lyj_filenode_get_meta(lyj_kv(dokan_file_info), fn);
    if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;
    fn->meta.file_size = byte_offset;
    lyj_filenode_set_meta(lyj_kv(dokan_file_info), fn);
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_set_allocation_size(LPCWSTR file_name, LONGLONG _alloc_size, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);
    lyj_filenode_t* fn = lyj_fn(dokan_file_info);
    lyj_filenode_get_meta(lyj_kv(dokan_file_info), fn);
    if (!fn->meta.is_exist) return STATUS_OBJECT_NAME_NOT_FOUND;
    if (_alloc_size < 0) return STATUS_SUCCESS;
    lyj_size_t alloc_size = (lyj_size_t)_alloc_size;
    if (alloc_size > fn->meta.file_size)
    {
        if (fn->meta.file_size < LYJ_HEAD_BUFFER_SIZE)
        {
#if LYJ_HEADER_BUFFER_KV
            lyj_filenode_head_buffer_get(lyj_kv(dokan_file_info), fn);
            fn->head_buffer.len = lyj_min(LYJ_HEAD_BUFFER_SIZE, alloc_size);
            lyj_memzero((char *)fn->head_buffer.buf + fn->meta.file_size, fn->head_buffer.len - fn->meta.file_size);
            lyj_filenode_head_buffer_set(lyj_kv(dokan_file_info), fn);
#else
            lyj_filenode_head_buffer_set(lyj_kv(dokan_file_info), fn, NULL, alloc_size - fn->meta.file_size, fn->meta.file_size);
#endif
        }
    }
    lyj_filenode_buffer_set_size(lyj_kv(dokan_file_info), fn, alloc_size);
    fn->meta.file_size = alloc_size;
    lyj_filenode_set_meta(lyj_kv(dokan_file_info), fn);
    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

static NTSTATUS DOKAN_CALLBACK lyj_dokan_find_streams(LPCWSTR file_name, PFillFindStreamData fill_find_stream_data, PVOID find_stream_context, PDOKAN_FILE_INFO dokan_file_info)
{
    lyj_wlog(L"start %ls %ls", file_name, lyj_fn(dokan_file_info)->file_name);

    lyj_wlog(L"end");
    return STATUS_SUCCESS;
}

void lyj_dokan_init_operations(DOKAN_OPERATIONS* dokan_operations)
{
    dokan_operations->ZwCreateFile = lyj_dokan_create_file;


    dokan_operations->ReadFile = lyj_dokan_read_file;
    dokan_operations->WriteFile = lyj_dokan_write_file;

    dokan_operations->FlushFileBuffers = lyj_dokan_flush_file_buffers;
    dokan_operations->GetFileInformation = lyj_dokan_get_file_information;
    dokan_operations->FindFiles = lyj_dokan_find_files;
    dokan_operations->FindFilesWithPattern = NULL;// lyj_dokan_find_files_with_pattern;
    dokan_operations->SetFileAttributes = lyj_dokan_set_file_attributes;
    dokan_operations->SetFileTime = lyj_dokan_set_file_time;
    dokan_operations->DeleteFile = lyj_dokan_delete_file;
    dokan_operations->DeleteDirectory = lyj_dokan_delete_directory;
    dokan_operations->MoveFile = lyj_dokan_move_file;
    dokan_operations->SetEndOfFile = lyj_dokan_set_end_of_file;
    dokan_operations->SetAllocationSize = lyj_dokan_set_allocation_size;
    dokan_operations->LockFile = lyj_dokan_lock_file;
    dokan_operations->UnlockFile = lyj_dokan_unlock_file;
    dokan_operations->GetFileSecurity = lyj_dokan_get_file_security;
    dokan_operations->SetFileSecurity = lyj_dokan_set_file_security;
    dokan_operations->GetDiskFreeSpace = lyj_dokan_dokan_get_disk_free_space;
    dokan_operations->GetVolumeInformation = lyj_dokan_get_volume_information;
    dokan_operations->Unmounted = lyj_dokan_unmounted;
    dokan_operations->FindStreams = lyj_dokan_find_streams;
    dokan_operations->Mounted = lyj_dokan_mounted;


    dokan_operations->Cleanup = lyj_dokan_cleanup;
    dokan_operations->CloseFile = lyj_dokan_close_file;

}